package main

import (
	"image"
	"image/color"
	"image/jpeg"
	"log"
	"os"
	"path/filepath"
	"time"
	"unicode/utf8"

	"github.com/BurntSushi/toml"
	"github.com/fogleman/gg"
	"github.com/nfnt/resize"
	"golang.org/x/image/bmp"

	"github.com/fsnotify/fsnotify"
)

type IDData struct {
	Name       string
	Sex        string
	Nation     string
	BornYear   string
	BornMonth  string
	BornDay    string
	Address    string
	IDCardNo   string
	GrantDept  string
	Expiration string
	PhotoFile  string
}

type DrawConfig struct {
	FrontFile      string
	FrontFile_Save string

	BackFile      string
	BackFile_Save string

	Font_Point      float64
	Font_Point_Name float64
	Font_Point_IDNo float64
	Save_Quality    float64

	Name_X           float64
	Name_Y           float64
	Name_FontSpace_2 float64
	Name_FontSpace_3 float64
	Name_FontSpace_4 float64
	Name_FontSpace_X float64

	Sex_X float64
	Sex_Y float64

	Nation_X float64
	Nation_Y float64

	BornYear_X float64
	BornYear_Y float64

	BornMonth_X float64
	BornMonth_Y float64

	BornDay_X float64
	BornDay_Y float64

	Address_X          float64
	Address_Y          float64
	Address_LineHeight float64
	Address_LineWidth  float64
	Address_FontSpace  float64

	IDCardNo_X         float64
	IDCardNo_Y         float64
	IDCardNo_FontSpace float64

	GrantDept_X float64
	GrantDept_Y float64

	Expiration_X float64
	Expiration_Y float64

	Photo_X           float64
	Photo_Y           float64
	Photo_Width       float64
	Photo_Opacity_1   float64
	Photo_Opacity_1_T float64
	Photo_Opacity_2   float64
	Photo_Opacity_2_T float64
}

var idDataFile string
var idStartFile string
var drawConfigFile string

var contentFont string
var idnoFont string

func draw() {
	defer log.Println("====================================")
	/*
		defer func() {
			err := os.Remove(idDataFile)
			if err != nil {
				log.Fatal("[os.Remove.idDataFile]", err)
			}
		}()
	*/
	defer func() {
		err := os.Remove(idStartFile)
		if err != nil {
			log.Fatal("[os.Remove.idStartFile] [ERR] ", err)
		} else {
			for {
				_, errf := os.Stat(idStartFile)
				if errf != nil && os.IsNotExist(errf) {
					break
				}
				time.Sleep(time.Duration(10) * time.Millisecond)
			}
			log.Println("[os.Remove.idStartFile] [SUCCESS]")
		}
	}()
	log.Println("====================================")

	// ***读取配置*** - 二代证数据

	var idData IDData
	if _, err := toml.DecodeFile(idDataFile, &idData); err != nil {
		log.Fatal("[toml.DecodeFile.idDataFile]", err)
	}
	log.Printf("%#v\n", idData)

	// ***读取配置*** - 绘制参数

	var drawConfig DrawConfig
	if _, err := toml.DecodeFile(drawConfigFile, &drawConfig); err != nil {
		log.Fatal("[toml.DecodeFile.drawConfigFile]", err)
	}
	log.Printf("%#v\n", drawConfig)

	// ***加载图片*** - front

	frontImg, err := gg.LoadImage(drawConfig.FrontFile)
	if err != nil {
		log.Fatal("[gg.LoadImage.front]", err)
		return
	}

	dcFront := gg.NewContextForImage(frontImg)
	dcFront.SetRGBA(1, 1, 1, 0)
	dcFront.SetRGB(0, 0, 0)

	log.Println("***加载图片*** - front")

	// ***加载图片*** - back

	backImg, err := gg.LoadImage(drawConfig.BackFile)
	if err != nil {
		log.Fatal("[gg.LoadImage.back]", err)
		return
	}

	dcBack := gg.NewContextForImage(backImg)
	dcBack.SetRGBA(1, 1, 1, 0)
	dcBack.SetRGB(0, 0, 0)

	log.Println("***加载图片*** - back")

	// ***加载图片*** - photo

	photoFile, err := os.Open(idData.PhotoFile)
	if err != nil {
		log.Fatal("[os.Open.photo]", err)
		return
	}
	defer photoFile.Close()
	photoImg, err := bmp.Decode(photoFile)
	if err != nil {
		log.Fatal("[bmp.Decode.photo]", err)
		return
	}

	photoImgScale := resize.Resize(uint(drawConfig.Photo_Width), 0, photoImg, resize.Lanczos3)
	photoImagOpacity := ImageOpacity(
		&photoImgScale,
		drawConfig.Photo_Opacity_1, drawConfig.Photo_Opacity_1_T,
		drawConfig.Photo_Opacity_2, drawConfig.Photo_Opacity_2_T)

	log.Println("***加载图片*** - photo")

	// ***绘制内容*** - front - 头像

	dcFront.DrawImage(photoImagOpacity, int(drawConfig.Photo_X), int(drawConfig.Photo_Y))

	log.Println("***绘制内容*** - front - 头像")

	// ***绘制内容*** - front - 身份证号

	if err := dcFront.LoadFontFace(idnoFont, drawConfig.Font_Point_IDNo); err != nil {
		log.Fatal("[dcFront.LoadFontFace.IDNo]", err)
		return
	}

	idnoX := drawConfig.IDCardNo_X
	idnoY := drawConfig.IDCardNo_Y
	for _, s := range idData.IDCardNo {
		dcFront.DrawString(string(s), idnoX, idnoY)
		w, _ := dcFront.MeasureString(string(s))
		idnoX += (w + drawConfig.IDCardNo_FontSpace)
	}

	log.Println("***绘制内容*** - front - 身份证号")

	// ***绘制内容*** - front - 姓名

	if err := dcFront.LoadFontFace(contentFont, drawConfig.Font_Point_Name); err != nil {
		log.Fatal("[dcFront.LoadFontFace.Name]", err)
		return
	}

	nameX := drawConfig.Name_X
	nameY := drawConfig.Name_Y
	nameFontSpace := 0.0
	switch utf8.RuneCountInString(idData.Name) {
	case 0, 1, 2:
		nameFontSpace = drawConfig.Name_FontSpace_2
	case 3:
		nameFontSpace = drawConfig.Name_FontSpace_3
	case 4:
		nameFontSpace = drawConfig.Name_FontSpace_4
	default:
		nameFontSpace = drawConfig.Name_FontSpace_X
	}

	for _, s := range idData.Name {
		dcFront.DrawString(string(s), nameX, nameY)
		w, _ := dcFront.MeasureString(string(s))
		nameX += (w + nameFontSpace)
	}

	log.Println("***绘制内容*** - front - 姓名")

	// ***绘制内容*** - front - 其它

	if err := dcFront.LoadFontFace(contentFont, drawConfig.Font_Point); err != nil {
		log.Fatal("[dcFront.LoadFontFace.Other]", err)
		return
	}

	dcFront.DrawString(idData.Sex, drawConfig.Sex_X, drawConfig.Sex_Y)
	dcFront.DrawString(idData.Nation, drawConfig.Nation_X, drawConfig.Nation_Y)
	dcFront.DrawString(idData.BornYear, drawConfig.BornYear_X, drawConfig.BornYear_Y)
	dcFront.DrawString(idData.BornMonth, drawConfig.BornMonth_X, drawConfig.BornMonth_Y)
	dcFront.DrawString(idData.BornDay, drawConfig.BornDay_X, drawConfig.BornDay_Y)

	addrLines := truncateText(dcFront, idData.Address, drawConfig.Address_LineWidth)
	for idx, line := range addrLines {
		addrX := drawConfig.Address_X
		addrY := drawConfig.Address_Y + float64(idx)*drawConfig.Address_LineHeight
		for _, s := range line {
			dcFront.DrawString(string(s), addrX, addrY)
			w, _ := dcFront.MeasureString(string(s))
			addrX += (w + drawConfig.Address_FontSpace)
		}
	}

	log.Println("***绘制内容*** - front - 其它")

	// ***绘制内容*** - back - 其它

	if err := dcBack.LoadFontFace(contentFont, drawConfig.Font_Point); err != nil {
		log.Fatal("[dcBack.LoadFontFace]", err)
		return
	}

	dcBack.DrawString(idData.GrantDept, drawConfig.GrantDept_X, drawConfig.GrantDept_Y)
	dcBack.DrawString(idData.Expiration, drawConfig.Expiration_X, drawConfig.Expiration_Y)

	log.Println("***绘制内容*** - back - 其它")

	// ***保存图像*** - front

	frontSave, err := os.Create(drawConfig.FrontFile_Save)
	if err != nil {
		log.Fatal("[os.Create.frontSave]", err)
		return
	}
	defer frontSave.Close()
	// 将文件保存输出，并设置压缩比
	err = jpeg.Encode(frontSave, dcFront.Image(), &jpeg.Options{Quality: int(drawConfig.Save_Quality)})
	if err != nil {
		log.Fatal("[jpeg.Encode.frontSave]", err)
		return
	}

	log.Println("***保存图像*** - front")

	// ***保存图像*** - back

	backSave, err := os.Create(drawConfig.BackFile_Save)
	if err != nil {
		log.Fatal("[os.Create.backSave]", err)
		return
	}
	defer backSave.Close()
	// 将文件保存输出，并设置压缩比
	err = jpeg.Encode(backSave, dcBack.Image(), &jpeg.Options{Quality: int(drawConfig.Save_Quality)})
	if err != nil {
		log.Fatal("[jpeg.Encode.backSave]", err)
		return
	}

	log.Println("***保存图像*** - back")
}

func main() {
	ex, err := os.Executable()
	if err != nil {
		log.Fatal("[os.Executable]", err)
	}
	exePath := filepath.Dir(ex)

	idDataFile = exePath + "/idg_data.toml"
	idStartFile = exePath + "/idg_start.toml"
	drawConfigFile = exePath + "/idg.toml"

	contentFont = exePath + "/font/content.TTF"
	idnoFont = exePath + "/font/idno.TTF"

	//创建一个监控对象
	watch, err := fsnotify.NewWatcher()
	if err != nil {
		log.Fatal("[fsnotify.NewWatcher]", err)
	}
	defer watch.Close()

	//添加要监控的对象，文件或文件夹
	err = watch.Add(exePath)
	if err != nil {
		log.Fatal("[watch.Add]", err)
	}

	log.Printf("[watch] [%s] [%s]\n", idDataFile, idStartFile)

	//我们另启一个goroutine来处理监控对象的事件
	go func() {
		for {
			select {
			case ev := <-watch.Events:
				{
					//判断事件发生的类型，如下5种
					// Create 创建
					// Write 写入
					// Remove 删除
					// Rename 重命名
					// Chmod 修改权限
					if ev.Op&fsnotify.Create == fsnotify.Create {
						if ev.Name == idStartFile {
							log.Println("[watch.Events] [Create]", ev.Name)
							draw()
						}
					}
				}
			case err := <-watch.Errors:
				{
					log.Println("[watch.Errors]", err)
					return
				}
			}
		}
	}()

	//循环
	select {}
}

// truncateText 根据参数切分字符串
// 入参：
// dc: gg的一个实例
// src:原始的字符串
// maxw: 最多宽换行
// 返回值：切分之后的字符串
func truncateText(dc *gg.Context, src string, maxw float64) []string {
	result := make([]string, 0)

	head := 0
	for tail, _ := range src {
		w, _ := dc.MeasureString(src[head:tail])
		if w > maxw {
			result = append(result, src[head:tail])
			head = tail

			w, _ = dc.MeasureString(src[tail:])
			if w <= maxw {
				result = append(result, src[tail:])
				break
			}
		}
	}

	if len(result) <= 0 {
		result = append(result, src)
	}

	return result
}

//将输入图像m的透明度变为原来的倍数。若原来为完成全不透明，则percentage = 0.5将变为半透明
func ImageOpacity(m *image.Image, p1 float64, t1 float64, p2 float64, t2 float64) *image.RGBA64 {
	bd := (*m).Bounds()
	dx := bd.Dx()
	dy := bd.Dy()

	newRGBA := image.NewRGBA64(bd)
	for i := 0; i < dx; i++ {
		for j := 0; j < dy; j++ {
			r, g, b, a := (*m).At(i, j).RGBA()

			if r > uint32(t1) && g > uint32(t1) && b > uint32(t1) {
				a1 := float64(a) * p1
				v := newRGBA.ColorModel().Convert(color.NRGBA64{R: uint16(r), G: uint16(g), B: uint16(b), A: uint16(a1)})
				r, g, b, a = v.RGBA()
			} else if r > uint32(t2) && g > uint32(t2) && b > uint32(t2) {
				a2 := float64(a) * p2
				v := newRGBA.ColorModel().Convert(color.NRGBA64{R: uint16(r), G: uint16(g), B: uint16(b), A: uint16(a2)})
				r, g, b, a = v.RGBA()
			}

			newRGBA.SetRGBA64(i, j, color.RGBA64{R: uint16(r), G: uint16(g), B: uint16(b), A: uint16(a)})
		}
	}

	return newRGBA
}
